// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use crate::error::Error;
use crate::state::{SerialportInfo, SerialportState};
use serde::{Deserialize, Serialize};
use serialport5::{
    ClearBuffer as SerialClearBuffer, DataBits as SerialDataBits, FlowControl as SerialFlowControl,
    Parity as SerialParity, StopBits as SerialStopBits,
};
use std::io::{Read, Write};
use crate::state::ReadData;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
use std::thread;
use std::time::Duration;
use tauri::{AppHandle, Runtime, State, Window, Emitter};

/// Number of bits per character
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataBits {
    /// 5 bits per character
    Five,
    /// 6 bits per character
    Six,
    /// 7 bits per character
    Seven,
    /// 8 bits per character
    Eight,
}

impl From<DataBits> for SerialDataBits {
    fn from(bits: DataBits) -> Self {
        match bits {
            DataBits::Five => SerialDataBits::Five,
            DataBits::Six => SerialDataBits::Six,
            DataBits::Seven => SerialDataBits::Seven,
            DataBits::Eight => SerialDataBits::Eight,
        }
    }
}

/// Flow control modes
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FlowControl {
    /// No flow control
    None,
    /// Flow control using XON/XOFF bytes
    Software,
    /// Flow control using RTS/CTS signals
    Hardware,
}

impl From<FlowControl> for SerialFlowControl {
    fn from(flow: FlowControl) -> Self {
        match flow {
            FlowControl::None => SerialFlowControl::None,
            FlowControl::Software => SerialFlowControl::Software,
            FlowControl::Hardware => SerialFlowControl::Hardware,
        }
    }
}

/// Parity checking modes
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Parity {
    /// No parity bit
    None,
    /// Parity bit sets odd number of 1 bits
    Odd,
    /// Parity bit sets even number of 1 bits
    Even,
}

impl From<Parity> for SerialParity {
    fn from(parity: Parity) -> Self {
        match parity {
            Parity::None => SerialParity::None,
            Parity::Odd => SerialParity::Odd,
            Parity::Even => SerialParity::Even,
        }
    }
}

/// Number of stop bits
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum StopBits {
    /// One stop bit
    One,
    /// Two stop bits
    Two,
}

impl From<StopBits> for SerialStopBits {
    fn from(bits: StopBits) -> Self {
        match bits {
            StopBits::One => SerialStopBits::One,
            StopBits::Two => SerialStopBits::Two,
        }
    }
}

/// Buffer types for clearing
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ClearBuffer {
    /// Input buffer (received data)
    Input,
    /// Output buffer (transmitted data)
    Output,
    /// Both input and output buffers
    All,
}

impl From<ClearBuffer> for SerialClearBuffer {
    fn from(buffer: ClearBuffer) -> Self {
        match buffer {
            ClearBuffer::Input => SerialClearBuffer::Input,
            ClearBuffer::Output => SerialClearBuffer::Output,
            ClearBuffer::All => SerialClearBuffer::All,
        }
    }
}

fn get_serialport<T, F: FnOnce(&mut SerialportInfo) -> Result<T, Error>>(
    state: State<'_, SerialportState>,
    path: String,
    f: F,
) -> Result<T, Error> {
    match state.serialports.lock() {
        Ok(mut map) => match map.get_mut(&path) {
            Some(serialport_info) => f(serialport_info),
            None => Err(Error::String("Serial port not found".to_string())),
        },
        Err(error) => Err(Error::String(format!(
            "Failed to acquire file lock! {}",
            error
        ))),
    }
}

/// Get serial port list
#[tauri::command]
pub fn available_ports() -> Vec<String> {
    let list = serialport5::available_ports().unwrap_or_else(|_| vec![]);
    let mut result_list: Vec<String> = vec![];

    for p in list {
        result_list.push(p.port_name);
    }

    result_list
}

#[tauri::command]
pub fn open<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
    baud_rate: u32,
    data_bits: Option<DataBits>,
    flow_control: Option<FlowControl>,
    parity: Option<Parity>,
    stop_bits: Option<StopBits>,
    timeout: Option<u64>,
) -> Result<(), Error> {
    match state.serialports.lock() {
        Ok(mut serialports) => {
            if serialports.contains_key(&path) {
                return Err(Error::String(format!("Serial port {} is open!", path)));
            }

            let port = serialport5::SerialPort::builder()
                .baud_rate(baud_rate)
                .data_bits(data_bits.map(Into::into).unwrap_or(SerialDataBits::Eight))
                .flow_control(
                    flow_control
                        .map(Into::into)
                        .unwrap_or(SerialFlowControl::None),
                )
                .parity(parity.map(Into::into).unwrap_or(SerialParity::None))
                .stop_bits(stop_bits.map(Into::into).unwrap_or(SerialStopBits::One))
                .read_timeout(Some(Duration::from_millis(timeout.unwrap_or(200))))
                .write_timeout(Some(Duration::from_millis(timeout.unwrap_or(200))))
                .open(path.clone())
                .map_err(|e| Error::String(format!("Failed to open serial port: {}", e)))?;

            let port_info = SerialportInfo {
                serialport: port,
                sender: None,
                thread_handle: None,
            };

            serialports.insert(path, port_info);
            // println!("Serial port {} is open!", path.clone());
            Ok(())
        }
        Err(error) => Err(Error::String(format!("Failed to acquire lock: {}", error))),
    }
}

/// Close the specified serial port
#[tauri::command]
pub fn close<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<(), Error> {
    match state.serialports.lock() {
        Ok(mut serialports) => {
            if let Some(port_info) = serialports.remove(&path) {
                // Signal the thread to stop
                if let Some(sender) = &port_info.sender {
                    sender.send(1).map_err(|e| {
                        Error::String(format!("Failed to cancel serial port data reading: {}", e))
                    })?;
                }

                // Wait for the thread to finish
                if let Some(handle) = port_info.thread_handle {
                    handle.join().map_err(|e| {
                        Error::String(format!("Failed to join thread: {:?}", e))
                    })?;
                }

                Ok(())
            } else {
                Err(Error::String(format!("Serial port {} is not open!", &path)))
            }
        }
        Err(error) => Err(Error::String(format!("Failed to acquire lock: {}", error))),
    }
}

/// Close all open serial ports
#[tauri::command]
pub fn close_all<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
) -> Result<(), Error> {
    match state.serialports.lock() {
        Ok(mut map) => {
            let mut errors = Vec::new();
            for (path, port_info) in map.drain() {
                if let Some(sender) = &port_info.sender {
                    if let Err(e) = sender.send(1) {
                        errors.push(format!("Failed to cancel port {}: {}", path, e));
                        continue;
                    }
                }
                if let Some(handle) = port_info.thread_handle {
                    if let Err(e) = handle.join() {
                        errors.push(format!("Failed to join thread for port {}: {:?}", path, e));
                    }
                }
            }
            if errors.is_empty() {
                Ok(())
            } else {
                Err(Error::String(format!(
                    "Errors during close: {}",
                    errors.join(", ")
                )))
            }
        }
        Err(error) => Err(Error::String(format!("Failed to acquire lock: {}", error))),
    }
}

/// Force close a serial port
#[tauri::command]
pub fn force_close<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<(), Error> {
    match state.serialports.lock() {
        Ok(mut map) => {
            if let Some(serial) = map.remove(&path) {
                if let Some(sender) = &serial.sender {
                    sender.send(1).map_err(|e| {
                        Error::String(format!("Failed to cancel serial port data reading: {}", e))
                    })?;
                }

                if let Some(handle) = serial.thread_handle {
                    handle.join().map_err(|e| {
                        Error::String(format!("Failed to join thread: {:?}", e))
                    })?;
                }
            }
            Ok(())
        }
        Err(error) => Err(Error::String(format!("Failed to acquire lock: {}", error))),
    }
}


#[tauri::command]
pub async fn cancel_read<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<(), Error> {
    get_serialport(state, path.clone(), |serialport_info| {
        if let Some(sender) = &serialport_info.sender {
            sender.send(1).map_err(|e| {
                Error::String(format!("Failed to cancel serial port data reading: {}", e))
            })?;
        }
        serialport_info.sender = None;
        Ok(())
    })
}

/// Read data from the serial port
#[tauri::command]
pub fn start_listening<R: Runtime>(
    app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
    timeout: Option<u64>,
    size: Option<usize>,
) -> Result<(), Error> {
    println!("Starting listening on port: {}", path);

    get_serialport(state.clone(), path.clone(), |port_info| {
        if port_info.sender.is_some() {
            println!("Existing listener found, stopping it first");
            if let Some(sender) = &port_info.sender {
                sender.send(1).map_err(|e| {
                    eprintln!("Failed to stop existing listener: {}", e);
                    Error::String(format!("Failed to stop existing listener: {}", e))
                })?;
            }
            port_info.sender = None;

            // Wait for thread to finish
            if let Some(handle) = port_info.thread_handle.take() {
                println!("Waiting for existing thread to finish");
                if let Err(e) = handle.join() {
                    eprintln!("Error joining thread: {:?}", e);
                }
            }
        }

        // Start listening immediately after opening
        let event_path = path.replace(".", "-").replace("/", "-");
        let read_event = format!("plugin-serial-read-{}", &event_path);
        let disconnected_event = format!("plugin-serial-disconnected-{}", &event_path);

        println!("Setting up port monitoring for: {}", read_event);

        let mut serial = port_info
            .serialport
            .try_clone()
            .map_err(|e| Error::String(format!("Failed to clone serial port: {}", e)))?;

        let (tx, rx): (Sender<usize>, Receiver<usize>) = mpsc::channel();
        port_info.sender = Some(tx);

        let app_clone = app.clone();
        let path_clone = path.clone();
        let thread_handle = thread::spawn(move || {
            loop {
                match rx.try_recv() {
                    Ok(_) => break,
                    Err(TryRecvError::Disconnected) => {
                        if let Err(e) = app_clone.emit(
                            &disconnected_event,
                            format!("Serial port {} disconnected!", &path_clone),
                        ) {
                            eprintln!("Failed to send disconnection event: {}", e);
                        }
                        break;
                    }
                    Err(TryRecvError::Empty) => {}
                }

                let mut buffer = vec![0; size.unwrap_or(1024)];
                match serial.read(&mut buffer) {
                    Ok(n) => {
                        if let Err(e) = app_clone.emit(
                            &read_event,
                            ReadData {
                                data: &buffer[..n],
                                size: n,
                            },
                        ) {
                            eprintln!("Failed to send data: {}", e);
                        }
                    }
                    Err(e) if e.kind() == std::io::ErrorKind::TimedOut => {}
                    Err(e) => {
                        eprintln!("Failed to read data: {}", e);
                        break; // Exit on error
                    }
                }
                thread::sleep(Duration::from_millis(timeout.unwrap_or(200)));
            }
        });
        port_info.thread_handle = Some(thread_handle);

        Ok({})
    })
}

#[tauri::command]
pub fn stop_listening<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<(), Error> {
    println!("Stopping listening on port: {}", path);

    get_serialport(state.clone(), path.clone(), |port_info| {
        if let Some(sender) = &port_info.sender {
            sender.send(1).map_err(|e| {
                Error::String(format!("Failed to cancel serial port data reading: {}", e))
            })?;
        }
        port_info.sender = None;
        port_info.thread_handle = None;

        Ok({})
    })
}


/// Read data from the serial port
#[tauri::command]
pub fn read<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
    timeout: Option<u64>,
    size: Option<usize>,
) -> Result<String, Error> {
    get_serialport(state.clone(), path.clone(), |serialport_info| {
        let mut buffer = vec![0; size.unwrap_or(1024)];
        serialport_info.serialport
            .set_read_timeout(Some(Duration::from_millis(timeout.unwrap_or(200))))
            .map_err(|e| Error::String(format!("Failed to set timeout: {}", e)))?;

        match serialport_info.serialport.read(&mut buffer) {
            Ok(n) => {
                let data = String::from_utf8_lossy(&buffer[..n]).to_string();
                Ok(data)
            },
            Err(e) if e.kind() == std::io::ErrorKind::TimedOut => Ok(String::new()),
            Err(e) => Err(Error::String(format!("Failed to read data: {}", e))),
        }
    })
}

/// Write data to the serial port
#[tauri::command]
pub fn write<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
    value: String,
) -> Result<usize, Error> {
    get_serialport(state, path.clone(), |serialport_info| {
        serialport_info
            .serialport
            .write(value.as_bytes())
            .map_err(|e| Error::String(format!("Failed to write data: {}", e)))
    })
}

/// Write binary data to the serial port
#[tauri::command]
pub fn write_binary<R: Runtime>(
    _app: AppHandle<R>,
    _window: Window<R>,
    state: State<'_, SerialportState>,
    path: String,
    value: Vec<u8>,
) -> Result<usize, Error> {
    get_serialport(state, path.clone(), |serialport_info| {
        serialport_info
            .serialport
            .write(&value)
            .map_err(|e| Error::String(format!("Failed to write binary data: {}", e)))
    })
}


/// Set the baud rate
#[tauri::command]
pub async fn set_baud_rate<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    baud_rate: u32,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .set_baud_rate(baud_rate)
            .map_err(Error::from)
    })
}

/// Set the data bits
#[tauri::command]
pub async fn set_data_bits<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    data_bits: DataBits,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .set_data_bits(data_bits.into())
            .map_err(Error::from)
    })
}

/// Set the flow control
#[tauri::command]
pub async fn set_flow_control<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    flow_control: FlowControl,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .set_flow_control(flow_control.into())
            .map_err(Error::from)
    })
}

/// Set the parity
#[tauri::command]
pub async fn set_parity<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    parity: Parity,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .set_parity(parity.into())
            .map_err(Error::from)
    })
}

/// Set the stop bits
#[tauri::command]
pub async fn set_stop_bits<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    stop_bits: StopBits,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .set_stop_bits(stop_bits.into())
            .map_err(Error::from)
    })
}

/// Set the RTS (Request To Send) control signal
#[tauri::command]
pub async fn write_request_to_send<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    level: bool,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .write_request_to_send(level)
            .map_err(Error::from)
    })
}

/// Set the DTR (Data Terminal Ready) control signal
#[tauri::command]
pub async fn write_data_terminal_ready<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    level: bool,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .write_data_terminal_ready(level)
            .map_err(Error::from)
    })
}

/// Read the CTS (Clear To Send) control signal state
#[tauri::command]
pub async fn read_clear_to_send<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<bool, Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .read_clear_to_send()
            .map_err(Error::from)
    })
}

/// Read the DSR (Data Set Ready) control signal state
#[tauri::command]
pub async fn read_data_set_ready<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<bool, Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .read_data_set_ready()
            .map_err(Error::from)
    })
}

/// Read the RI (Ring Indicator) control signal state
#[tauri::command]
pub async fn read_ring_indicator<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<bool, Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .read_ring_indicator()
            .map_err(Error::from)
    })
}

/// Read the CD (Carrier Detect) control signal state
#[tauri::command]
pub async fn read_carrier_detect<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<bool, Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .read_carrier_detect()
            .map_err(Error::from)
    })
}

/// Get the number of bytes available to read
#[tauri::command]
pub async fn bytes_to_read<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<u32, Error> {
    get_serialport(state, path, |port_info| {
        port_info.serialport.bytes_to_read().map_err(Error::from)
    })
}

/// Get the number of bytes waiting to be written
#[tauri::command]
pub async fn bytes_to_write<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<u32, Error> {
    get_serialport(state, path, |port_info| {
        port_info.serialport.bytes_to_write().map_err(Error::from)
    })
}

/// Clear input/output buffers
#[tauri::command]
pub async fn clear_buffer<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
    buffer_to_clear: ClearBuffer,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info
            .serialport
            .clear(buffer_to_clear.into())
            .map_err(Error::from)
    })
}

/// Start break signal transmission
#[tauri::command]
pub async fn set_break<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info.serialport.set_break().map_err(Error::from)
    })
}

/// Stop break signal transmission
#[tauri::command]
pub async fn clear_break<R: Runtime>(
    _app: AppHandle<R>,
    state: State<'_, SerialportState>,
    path: String,
) -> Result<(), Error> {
    get_serialport(state, path, |port_info| {
        port_info.serialport.clear_break().map_err(Error::from)
    })
}
